﻿(function (root) {
	var _ = root._;

	var previousjmvc = root.jmvc;

	var jmvc = root.jmvc = {
	};

	jmvc.noConflict = function () {
		root.jmvc = previousjmvc;
		return this;
	};

	var Collection = jmvc.Collection = function () {
		this.add = function (l) {
			var _this = this;
			if (!_.isArray(l))
				l = [l];
			_.each(l, function (properties) {
				var item;
				if (properties.name) {
					item = _this.find(properties.name);
				}
				if (item)
					$.extend(true, item, properties);
				else {
					if (_.isFunction(_this.Factory)) {
						item = new _this.Factory();
						$.extend(true, item, properties);
					}
					else
						item = properties;
				}
				if (!item.name)
					return;
				_this[item.name] = item;
				if (_this.onAddComplete)
					_this.onAddComplete(item);
			});
		}
		this.find = function (n) {
			return this[n];
		}
		this.findOrCreate = function (name) {
			var item = this.find(name);
			if (!item && this.Factory) {
				item = new this.Factory(name);
				this.add(item);
			}
			return item;
		}
	}

	jmvc.modules = new Collection();
	jmvc.modules.onAddComplete = function (m) {
		if (m.onload)
			m.load();
	}

	jmvc.modules.Module = function (name) {
		this.name = name;

		this.controllers = new jmvc.Controllers();
		this.controllers.Factory = new jmvc.View;
		this.action = function (c, a, m) {
			this.controllers.action(c, a, m);
		}
		this.getId = function () {
			return 'jmvc-module-' + this.name;
		}
		this.views = new jmvc.Views();
		this.views.Factory = jmvc.View;
		this.view = function (name, model) {
			var v = this.views.findOrCreate(name);
			if (v)
				v.render(model);
		}


		this.modelRouters = new ModelRouters();
		this.load = function () {
			var id = this.getId();
			var _this = this;
			jmvc.loadScript(this.url, id, function () { if (_this.onload) _this.onload(); });
		}
	}


	jmvc.module = function (name) {
		return jmvc.modules.findOrCreate(name);
	};

	jmvc.Controllers = function () {
		this.action = function (c, a, m) {
			if (!a)
				a = 'index';
			var c = this[c];
			if (c && c.actions && c.actions[a]) {
				c.actions[a].call(c, m);
			}
		}
	}
	jmvc.Controllers.prototype = new Collection();

	jmvc.Controller = function (name) {
		this.name = name;
		this.view = function (name, model) {
			var v = this.module.views(name);
			if (v && model)
				v.render(model);
			return v;
		}
	}

	jmvc.Views = function () {

	}
	jmvc.Views.prototype = new Collection();

	jmvc.View = function (name) {
		this.name = name;
		this.render = function (model) {
			var _this = this;
			var $el = $(this.el);
			if ($el.length) {
				if (!this.templateId) {
					$.get(this.url, function (data) {
						$el.html(data);
						//jmvc.addTemplate(
					});
				}
				else {
					var $t = $(this.templateId);
					if ($t.length == 0) {
						$.get(this.url, function (data) {
							$('body').append(data);
							$t = $(_this.templateId);
							if ($t.length) {
								var thtml = $t.html();
								var html = jmvc.template.render(thtml, model, _this);
								$el.html(html);
							}
						});
					}
					else
						$el.html(jmvc.template.render($t.html(), model, _this));
				}
			}
		}
		this.validate = function () {
			alert('test');
			return false;
		}
	}

	var ModelRouters = jmvc.ModelRouters = function () {
		this.fetch = function (name, success, params) {
			var m = this[name]
			if (m) {
				if (m.data) {
					if (success)
						success(m.data);
				}
				$.get(this.replaceParams(m.url, params), function (data) {
					m.data = data;
					if (success)
						success(data);
				});
			}
		}
		this.replaceParams = function (url, params) {
			if (params) {
				for (var p in params) {
					url = url.replace(new RegExp(':' + p, 'g'), params[p]);
				}
			}
			return url;
		}
	}
	ModelRouters.prototype = new Collection();


	jmvc.addToHead = function (e) {
		var heads = document.getElementsByTagName("head");
		if (heads && heads.length > 0)
			heads[0].appendChild(e);
	}
	jmvc.loadScript = function (url, id, onload) {
		if (!url)
			return;
		var _onload = function () {
			if (onload)
				onload();
		};
		if ($('#' + id).length > 0)	//return if already in the DOM
		{
			_onload();
			return;
		}
		var script = document.createElement('script');
		if (id)
			script.id = id;
		script.type = 'text/javascript';
		jmvc.addToHead(script);
		script.onload = _onload;
		script.src = url;
	}
	jmvc.addTemplate = function (id, content) {
		if (id && $('#' + id).length > 0)	//return if already in the DOM
			return;
		var script = document.createElement('script');
		if (id)
			script.id = id;
		script.type = 'text/template';
		jmvc.addToHead(script);
	}

	jmvc.modules.Factory = jmvc.modules.Module;


	//----------------- TEMPLATE ENGINE ------------------------------
	jmvc.template = {}
	jmvc.template.setOptions = function (o) {
		if (o.templateFunction)
			jmvc.template.render = o.templateFunction;
	}
	jmvc.template.render = function (template, model, view) {
		var $template = $('<div>' + template + '</div>');
		jmvc.template.recursive({ $nodes: $template, model: model, view: view });
		return $template.html();
	}

	jmvc.template.recursive = function (scope) {
		scope.$nodes.each(function (i, e) {
			var $this = $(this);
			var subScope = {
				module: scope.module,
				controller: scope.controller,
				model: scope.model,
				view: scope.view
			}
			for (var key in jmvc.templateHandlers) {
				var templateKey = jmvc.templateAttributePrefix + key;
				var value = $this.attr(templateKey);
				if (value != null) {
					var h = jmvc.templateHandlers[key];
					$.extend(true, subScope, { $node: $this, key: templateKey, value: value });
					h.handler(subScope);
					if (h.remove)
						$this.removeAttr(templateKey);
					if (h.stop || subScope.stop)
						return;
				}
			}
			$.extend(true, subScope, { $nodes: $this.children(), key: null, value: null });
			jmvc.template.recursive(subScope);
		});
	}

	jmvc.templateAttributePrefix = ''; // 'jmvc-';
	jmvc.templateHandlers = {
		'module': {
			handler: function (scope) {
				scope.module = scope.value;
			}
		},
		'controller': {
			handler: function (scope) {
				scope.controller = scope.value;
			}
		},
		'repeat': {
			handler: function (s) {
				if (!s.model)
					return;
				if (s.value) {
					if (s.value != '_self')
						s.model = s.model[s.value];
					if (s.model && s.model.length > 0) {
						var $copy = s.$node.removeAttr(s.key).clone();
						for (var i = 0; i < s.model.length; i++) {
							var iScope = { $nodes: s.$node, model: s.model[i] }
							if (i == 0) {
								jmvc.template.recursive(iScope);
							} else {
								var $next = $copy.clone();
								iScope.$nodes = $next;
								jmvc.template.recursive(iScope);
								s.$node.after($next);
								s.$node = $next;
							}
						}
					}
					else
						s.$node.remove();
				}
			},
			stop: true
		},
		'template': {
			handler: function (s) {
				var config = {};
				var overrides = $.parseJSON(s.value);
				$.extend(true, config, overrides);
				var $t = $('#' + config.template);
				var subModel = s.model;
				if (s.model && config.model)
					subModel = s.model[config.model];
				var html = jmvc.template.render($t.html(), subModel);
				if (config.remove) {
					$e.after(html);
					$e.remove();
				}
				else
					$e.html(html);
			},
			stop: true,
			remove: true
		},
		'bind': {
			handler: function (s) {
				if (s.model) {
					var content = s.model;
					if (s.value != '_self')
						content = s.model[s.value];
					s.$node.html(content);
				}
			},
			remove: true
		},
		'action': {
			handler: function (s) {
				var config = { event: 'click', module: s.module, controller: s.controller };
				var overrides = $.parseJSON(s.value);
				$.extend(true, config, overrides);

				var tag = s.$node.prop("tagName").toLowerCase();
				var eventId = jmvc.uniqueId();
				s.$node.attr('jmvc-event-id', eventId);
				var sel = tag + "[jmvc-event-id='" + eventId + "']";

				var subModel = s.model;
				if (s.model && config.model)
					subModel = s.model[config.model];

				//impove: better to bind the delegated event to the first parent already on the DOM
				$(document).on(config.event, sel, function () {
					var module = jmvc.module(config.module);
					if (config.validate && s.view) {
						if (!s.view.validate())
							return;
					}
					module.action(config.controller, config.action, subModel);
				});
				var i = 0;
			},
			remove: true
		},
		'condition': {
			handler: function (s) {
				var isTrue = false;
				if (s.model) {
					s.model._callEval = function () {
						return eval(s.value);
					};
					var isTrue = s.model._callEval();
					s.model._callEval = null;
				}
				else
					isTrue = eval.call(s.model, s.value);
				if (!isTrue) {
					s.$node.remove();
					s.stop=true;
				}
			},
			remove: true
		}
	}

	jmvc.uniqueIdSeed = 1;
	jmvc.uniqueId = function () {
		var d = new Date();
		var m = d.getMilliseconds() + "";
		var u = ++d + m + (++jmvc.uniqueIdSeed === 10000 ? (jmvc.uniqueIdSeed = 1) : jmvc.uniqueIdSeed);
		return u;
	}
})(this);